﻿namespace NTLib.Core.Reflection
{
    using Pool;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Threading.Tasks;

    /// <summary>
    /// 
    /// </summary>
    [DataContract]
    public sealed class CollectionDescriptor : PoolItem, IElementDescriptor
    {
        #region Fields

        private static readonly ObjectPool<CollectionDescriptor> s_pool = new ObjectPool<CollectionDescriptor>();

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="ObjectDescriptor"/> class.
        /// </summary>
        public CollectionDescriptor()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the pool.
        /// </summary>
        public static IPool Pool
        {
            get { return s_pool; }
        }

        /// <summary>
        /// Gets or sets the properties.
        /// </summary>
        [DataMember]
        public List<IElementDescriptor> Elements { get; set; }

        /// <summary>
        /// Gets or sets the name of the assembly qualifier.
        /// </summary>
        /// <value>
        /// The name of the assembly qualifier.
        /// </value>
        [DataMember]
        public string AssemblyQualifiedName { get; set; }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        [DataMember]
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the name of the type.
        /// </summary>
        [DataMember]
        public string TypeName { get; set; }

        #endregion

        #region Methods

        /// <summary>
        /// Reflections the create.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        public static CollectionDescriptor ReflectionCreate(IEnumerable obj, string name = null)
        {
            return ReflectionCreateInternal(obj, name, new HashSet<object>());
        }

        /// <summary>
        /// Reflections the create internal.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="lastObjects">The last objects.</param>
        /// <returns></returns>
        internal static CollectionDescriptor ReflectionCreateInternal(IEnumerable obj, string name, HashSet<object> lastObjects)
        {
            if (obj == null)
                throw new ArgumentNullException(nameof(obj));

            CollectionDescriptor desc = s_pool.Get();
            desc.Initialize(obj.GetType(), name);

            List<IElementDescriptor> elements = new List<IElementDescriptor>();

            foreach (var o in obj)
                elements.Add(ObjectDescriptor.CreateInternal(o, (o == null) ? typeof(object) : o.GetType(), null, lastObjects));

            desc.Elements = elements;

            return desc;
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return JsonString();
        }

        /// <summary>
        /// Jsons the string.
        /// </summary>
        /// <returns></returns>
        public string JsonString()
        {
            StringBuilder builder = new StringBuilder();
            JsonString(builder, 0);
            return builder.ToString();
        }

        /// <summary>
        /// Jsons the string.
        /// </summary>
        /// <param name="builder">The builder.</param>
        /// <param name="lvl">The level.</param>
        /// <param name="displayNull">if set to <c>true</c> [display null].</param>
        public void JsonString(StringBuilder builder, int lvl = 0, bool displayNull = false)
        {
            if (!string.IsNullOrEmpty(Name))
            {
                builder.Append(' ', lvl * 4);
                builder.Append(Name);
                builder.Append(" : ");
            }

            var localProp = Elements;
            if (!displayNull)
            {
                localProp = Elements.Where((prop) =>
                {
                    return !(prop is ValueDescriptor && ((ValueDescriptor)prop).Value == ObjectDescriptor.NULL_STR);
                }).ToList();
            }

            builder.Append("[ ");
            if (localProp.Count > 1)
                builder.AppendLine();
            bool first = true;
            IElementDescriptor prev = null;
            foreach (var prop in localProp)
            {

                if (!first)
                {
                    if (prev != null && !(prev is ValueDescriptor))
                        builder.Append(' ', (lvl + 1) * 4);
                    builder.AppendLine(",");
                }
                first = false;
                prop.JsonString(builder, (!(prop is ValueDescriptor) || localProp.Count > 1) ? lvl + 1 : 0, displayNull);
                prev = prop;
            }

            if (localProp.Count > 1)
            {
                builder.AppendLine();
                builder.Append(' ', lvl * 4);
            }
            builder.Append("]");
        }

        /// <summary>
        /// Called when the pool item is reset.
        /// Everything MUST be initialized on this method.
        /// </summary>
        protected override void OnReset()
        {
            this.TypeName = null;
            this.Name = null;
            this.AssemblyQualifiedName = null;

            if (this.Elements != null)
            {
                var props = this.Elements.ToArray();
                this.Elements = null;

                foreach (var prop in props)
                {
                    try
                    {
                        prop.Dispose();
                    }
                    catch
                    {

                    }
                }
            }
        }

        /// <summary>
        /// Initializes the specified type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="name">The name.</param>
        public void Initialize(Type type, string name)
        {
            this.AssemblyQualifiedName = type.AssemblyQualifiedName;
            this.Name = name;
            this.TypeName = type.Name;
        }

        #endregion
    }
}
